package com.ruoyi.websocket.core;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.TypeUtil;
import com.ruoyi.websocket.config.INettyConfig;
import com.ruoyi.websocket.entity.Pair;
import com.ruoyi.websocket.handler.HeartbeatHandler;
import com.ruoyi.websocket.handler.HttpRequestHandler;
import com.ruoyi.websocket.handler.TextHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * netty服务
 *
 * @author xinian
 * @date 2025/1/14
 */
@Slf4j
//@ConditionalOnClass(value = WebsocketApplication.class)
public class WebsocketServer<E extends INettyConfig> {

    @Resource
    private WebsocketServer websocketServer;
    @Resource
    private TaskExecutor taskExecutor;

    @Autowired
    protected E nettyConfig;
    // 用于处理I/O事件，如连接请求
    private EventLoopGroup bossGroup;
    // 用于处理业务逻辑
    private EventLoopGroup workerGroup;


    @PostConstruct
    public void init() {
        if (System.getProperty("os.name" ).compareToIgnoreCase("Windows" )>0) {
            bossGroup = new NioEventLoopGroup(nettyConfig.getBossGroupThreadNum());
            workerGroup = new NioEventLoopGroup(nettyConfig.getWorkerGroupThreadNum());
        }else{
            bossGroup = new EpollEventLoopGroup(nettyConfig.getBossGroupThreadNum());
            workerGroup = new EpollEventLoopGroup(nettyConfig.getWorkerGroupThreadNum());
        }
        taskExecutor.execute(() -> websocketServer.start());

    }

    public void start() {
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);//优雅关闭
            for (Pair<String, String> option : nettyConfig.getOptions()) {
                ParameterizedTypeImpl actualType = (ParameterizedTypeImpl)TypeUtil.getActualType(ChannelOption.class, ChannelOption.class.getDeclaredField(option.getKey()));
                String typeName = actualType.getActualTypeArguments()[0].getTypeName();
                Class<?> aClass = Class.forName(typeName);
                Object value = ReflectUtil.getMethodByName(aClass, "valueOf" ).invoke(null, option.getValue());
                serverBootstrap.option(ChannelOption.valueOf(option.getKey()), value);
                log.info("Netty 配置ServerChannel选项参数-options：{} = {}", option.getKey(), option.getValue());
            }
            for (Pair<String, String> childOption : nettyConfig.getChildOptions()) {
                ParameterizedTypeImpl actualType = (ParameterizedTypeImpl)TypeUtil.getActualType(ChannelOption.class, ChannelOption.class.getDeclaredField(childOption.getKey()));
                String typeName = actualType.getActualTypeArguments()[0].getTypeName();
                Class<?> aClass = Class.forName(typeName);
                Object value = ReflectUtil.getMethodByName(aClass, "valueOf" ).invoke(null, childOption.getValue());
                serverBootstrap.childOption(ChannelOption.valueOf(childOption.getKey()), value);
                log.info("Netty 配置客户端Channel参数-childOptions：{} = {}", childOption.getKey(), childOption.getValue());
            }
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            // 添加HTTP编解码器，用于解析HTTP握手请求
                            pipeline.addLast(new HttpServerCodec());
                            pipeline.addLast(new ChunkedWriteHandler());
                            // 用于处理HTTP消息体（如POST请求），对于WebSocket握手GET请求，它也会聚合头信息
                            pipeline.addLast(new HttpObjectAggregator(nettyConfig.getMaxContentLength()));
                            // 添加自定义的HTTP请求处理器，专门拦截WebSocket升级请求
                            pipeline.addLast(new HttpRequestHandler(nettyConfig));
                            //TODO 需要修复客户端随便上送连接的问题.
                            // 添加WebSocket协议处理器，处理握手和帧,可以使用多个，HttpRequestHandler要放行
                            pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getWebsocketPath()));
                            // 关键：添加空闲状态检测处理器
                            // 参数：读空闲时间, 写空闲时间, 所有操作空闲时间, 时间单位
                            pipeline.addLast(new IdleStateHandler(nettyConfig.getReaderIdleTime(), nettyConfig.getWriterIdleTime(), 0, TimeUnit.SECONDS));
                            // 添加自定义的空闲状态处理处理器
                            pipeline.addLast(new HeartbeatHandler(nettyConfig));
                            // 添加最终的业务逻辑处理器
                            pipeline.addLast(new TextHandler(nettyConfig));
                        }
                    });
            if (System.getProperty("os.name" ).compareToIgnoreCase("Windows" )>0) {
                serverBootstrap.channel(NioServerSocketChannel.class);
            }else{
                serverBootstrap.channel(EpollServerSocketChannel.class);
            }
            ChannelFuture channelFuture = serverBootstrap.bind(nettyConfig.getPort()).sync();
//                ChannelFuture channelFuture = serverBootstrap.bind(10085).sync();
            log.info("Netty 服务端启动成功" );
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("netty启动失败：{}", e.getMessage());
        }
    }

    @PreDestroy
    public void stop() {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully();
        }
        if (workerGroup != null) {
            workerGroup.shutdownGracefully();
        }
    }

}
